module dice.render.matrix;

import dice.render.structs;
import cr = dice.render.cairo;
import std.math;

/** A transformation matrix */
struct Matrix
{
	float xx=1;	/// xx component
	float yx=0;	/// yx component
	float xy=0;	/// xy component
	float yy=1;	/// yy component
	float x0=0;	/// x component
	float y0=0;	/// y component
	
	/*this ()
	{
		xx = 1;
		yy = 1;
	}*/
	
	/*this (double a, double b, double c, double d, double tx, double ty)
    {
    	xx = a;
    	yx = b;
    	xy = c;
    	yy = d;
    	x0 = tx;
    	y0 = ty;
    }*/
    
    /+Matrix copy ()
    {
    	return new Matrix(xx,yx,xy,yy,x0,y0);
    }+/
    
    cr.Matrix toCairo ()
    {
    	cr.Matrix m;
    	m.xx = xx;
    	m.yx = yx;
    	m.xy = xy;
    	m.yy = yy;
    	m.x0 = x0;
    	m.y0 = y0;
    	return m;
    }
    
    void setAffine (double a, double b, double c, double d, double tx, double ty)
    {
    	xx = a;
    	yx = b;
    	xy = c;
    	yy = d;
    	x0 = tx;
    	y0 = ty;
    }
    
    double determinant ()
    {
    	return xx * yx - xy * yy;
    }
    
    /** Returns true if this matrix can be inverted */
    bool canInvert ()
    {
    	return determinant != 0.0;
    }
    
    /** Multiplies all values by s */
    Matrix multiplied (float s)
    {
    	Matrix m = *this;
    	
    	m.xx *= s;
    	m.yx *= s;
    	m.xy *= s;
    	m.yy *= s;
    	m.x0 *= s;
    	m.y0 *= s;
    	
    	return m;
    }
    
    private void computeAdjoint ()
	{
		double a, b, c, d, tx, ty;
		a = xx;
		b = yx;
		c = xy;
		d = yy;
		tx = x0;
		ty = y0;
		
		xx = d;
		yx = -b;
		xy = -c;
		yy = a;
		x0 = c * ty - d * tx;
		y0 = b * tx - a * ty;
	}
    
    /** Inverts the matrix */
    Matrix inverted ()
    {
    	Matrix m = *this;
    	m.computeAdjoint();
    	m = m.multiplied(1/determinant);
    	return m;
    }
    
    /** Multiplies the matrix */
    static Matrix multiplied (Matrix a, Matrix b)
    {
    	Matrix r;
    	
    	r.xx = a.xx * b.xx + a.yx * b.xy;
    	r.yx = a.xx * b.yx + a.yx * b.yy;

   		r.xy = a.xy * b.xx + a.yy * b.xy;
    	r.yy = a.xy * b.yx + a.yy * b.yy;

    	r.x0 = a.x0 * b.xx + a.y0 * b.xy + b.x0;
    	r.y0 = a.x0 * b.yx + a.y0 * b.yy + b.y0;
    	
    	return r;
    }
    
    /** For convenience */
    Matrix opMul (Matrix b)
    {
    	return multiplied(*this,b);
    }
    
    /** Returns a copy of this matrix, with the x/y origin moved */
    Matrix translated (float x, float y)
    {
    	return multiplied(Matrix(1,0,0,1,x,y), *this);
    }
    
    /** Returns a copy of this matrix, rotated in radians */
    Matrix rotated (Radians rad)
    {
    	float c = cos(rad.radians);
    	float s = sin(rad.radians);
    	
    	Matrix m = *this;
    	m.xx += c;
    	m.yx += s;
    	m.xy -= s;
    	m.yy += c;
    	return m;
    	
    	//return *this * Matrix(c,s,-s,c,0,0);
    }
    
    /** Same as rotated, but rotates around a given point (FIXME!!) */
    Matrix rotated (Radians rad, float x, float y)
    {
    	float c = cos(rad.radians);
    	float s = sin(rad.radians);
    	
    	return multiplied(Matrix(c,s,-s,c,0,0), this.translated(-x,-y));
    }
    
    /** Returns a copy of this matrix scaled on the x and y axis */
    Matrix scaled (float x, float y)
    {
    	return multiplied(Matrix(x,0,0,y,0,0), *this);
    }
    
    /** Returns a copy of this matrix, scaled by the same value on both axis */
    Matrix scaled (float f)
    {
    	return scaled(f,f);
    }
    
    /* Transformations */
    /** Apply this matrix to a size/distance */
    Size transform (Size s)
    {
    	return Size(s.w * xx + s.h * xy, s.w * yx + s.h * yy);
    }
    
    /** Apply this matrix to a point */
    Point transform (Point p)
    {
    	return Point(p.x * xx + p.y * yy + x0, p.x * yx + p.y * yy + y0);
    }
    
    /** Return a rectangle that fits around the one given, after transformation */
    Rect transform (Rect r)
    {
    	Point p1 = Point(r.x1 * xx + r.y1 * yy + x0, r.x1 * yx + r.y1 * yy + y0);
    	Point p2 = Point(r.x2 * xx + r.y2 * yy + x0, r.x2 * yx + r.y2 * yy + y0);
    	//return Rect.between(p1, p2); - factor it out!
    	return Rect(fmin(p1.x,p2.x), fmin(p1.y,p2.y), fmax(p1.x,p2.x), fmax(p1.y,p2.y));
    }
    
    //char[] toString ()
    //{
    //	return "Matrix(" ~ xx.toString ~ ", " ~ yx.toString ~ ",...)";
    //}
}
